package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;

import com.heima.model.common.constants.article.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;

import com.heima.model.mess.app.ArticleVisitStreamMess;
import com.heima.utils.common.DateUtils;
import io.seata.common.util.StringUtils;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Transactional
public class HotArticleServiceImpl implements HotArticleService {
    @Autowired
    ApArticleMapper apArticleMapper;


    /**
     * 计算热文章
     */

    @Override
    public void computerHotArticle() {

        //1查询5天前的数据（已上架，未删除）文章数据
        //1。1查询当天
        String format = LocalDateTime.now().minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM=dd 00:00:00"));
        List<ApArticle> articleList = apArticleMapper.selectArticleByDate(format);
        if (CollectionUtils.isEmpty(articleList)) {
            return;
        }

        //2计算文章分值
        List<HotArticleVo> hotArticleList = computerHotArticle(articleList);
        //3每一个频道缓存热点较高的30条文章

        cacheTagToRedis(hotArticleList);
    }

    /**
     * 更新文章
     *
     * @param mess
     */
    @Override
    public void updateApArticle(ArticleVisitStreamMess mess) {
        //1。根据文章id查询出文章数据
        ApArticle article = apArticleMapper.selectById(mess.getArticleId());
        if (article == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");//501
        }
        //2。更新文章  各个行为的值
        int newComment =(int) (article.getComment() == null ? 0 : article.getComment() + mess.getComment());
        article.setComment(newComment<0?0:newComment);
        int newView=(int) (article.getViews() == null ? 0 : article.getViews() + mess.getView());
        article.setViews(newView<0?0:newView);
        int newLikes=(int) (article.getLikes() == null ? 0 : article.getLikes() + mess.getLike());
        article.setLikes(newLikes<0?0:newLikes);
        int newCollection=(int) (article.getCollection() == null ? 0 : article.getCollection() + mess.getCollect());
        article.setCollection(newCollection<0?0:newCollection);
        apArticleMapper.updateById(article);
        //3。计算文章得分
        Integer score = computerScore(article);
        //4。判断文章是否今日发布，如果是热度*3
        String nowStr = DateUtils.dateToString(new Date());//今日
        String publisher = DateUtils.dateToString(article.getPublishTime());//发布日期
        if (nowStr.equals(publisher)) {
            score = score * 3;
        }

        //5。查询频道对应热点文章，替换分较低文章
        updateApArticleCache(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());


        //6。查询推荐频道对应热点文章，替换分较低文章
        updateApArticleCache(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);

    }

    /**
     * 更新缓存的key
     *
     * @param article  当前文章数据
     * @param score    该文章最新得分
     * @param cacheKey 要更新缓存的key
     */

    private void updateApArticleCache(ApArticle article, Integer score, String cacheKey) {
        //1.获取redis中热点文章的缓存数据
        String hotArticleListJson = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(hotArticleListJson)) {
            boolean isHas = false;
            //2.判断当前文章是否已经存在于热点文章中
            List<HotArticleVo> hotArticleVoList = JSONArray.parseArray(hotArticleListJson, HotArticleVo.class);
            //如果存在直接更新热度值
            for (HotArticleVo hotArticleVo : hotArticleVoList) {
                if (hotArticleVo.getId().equals(article.getId())) {
                    hotArticleVo.setScore(score);//重新设置得分
                    isHas = true;
                    break;
                }
            }
            //3.如果不存在  直接将文章封装成vo对象，加入到热点文章集合中
            if (!isHas) {
                HotArticleVo articleVo = new HotArticleVo();
                BeanUtils.copyProperties(article, articleVo);
                articleVo.setScore(score);
                hotArticleVoList.add(articleVo);

            }            //4.将集合按照文章得分降序排序，取前30条数据即可
            hotArticleVoList = hotArticleVoList.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .limit(30)
                    .collect(Collectors.toList());

            //5.缓存到redis中
            redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleVoList));

        }
    }

    @Autowired
    AdminFeign adminFeign;

    /**
     * 按频道缓存文章
     * 按热度降序排序  每个频道只缓存热度最高的30条文章
     * 推荐频道  缓存所有文章 热度最高的30条文章
     *
     * @param hotArticleList
     */
    private void cacheTagToRedis(List<HotArticleVo> hotArticleList) {
        // 1.查询所有的频道列表
        ResponseResult<List<AdChannel>> result = adminFeign.selectChannels();
        if (!result.checkCode()) {
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR, "用户远程调用失败");
        }
        List<AdChannel> adChannelList = result.getData();
        //2.遍历频道列表，筛选当前频道下的文章
        for (AdChannel adChannel : adChannelList) {
            List<HotArticleVo> articleByChannel = hotArticleList.stream()
                    //当前频道下的列表
                    .filter(hotArticle -> hotArticle.getChannelId().equals(adChannel.getId()))
                    .collect(Collectors.toList());
            sortAndCache(articleByChannel, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + adChannel.getId());
        }
        //3. 按频道缓存文章
        sortAndCache(hotArticleList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);


        // 按热度降序排序  每个频道只缓存热度最高的30条文章


    }

    @Autowired
    StringRedisTemplate redisTemplate;//字符串序列化

    private void sortAndCache(List<HotArticleVo> hotArticleList, String cacheKey) {
        //截取文章前30条
        List<HotArticleVo> hotArticleVoList = hotArticleList.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        //30条文章缓存到redis
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleVoList));


    }

    /**
     * 计算文章分值computerHotArticle
     *
     * @param articleList
     * @return
     */
    private List<HotArticleVo> computerHotArticle(List<ApArticle> articleList) {
        //1循环遍历文章列表
        return articleList.stream().map(article -> {
            //计算每篇文章得分，按照权重计算
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(article, hotArticleVo);
            Integer score = computerScore(article);
            hotArticleVo.setScore(score);
            //将article封装成vo对象
            return hotArticleVo;
        }).collect(Collectors.toList());

    }

    /**
     * 计算computerScore
     *
     * @param article
     * @return
     */
    private Integer computerScore(ApArticle article) {
        //按权重计算得分
        Integer score = 0;
        if (article.getLikes() == null) {
            score += article.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (article.getViews() == null) {
            score += article.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if (article.getCollection() == null) {
            score += article.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        if (article.getComment() == null) {
            score += article.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }

        return score;
    }
}
